/**
 * @copyright 2015 Indie Semiconductor.
 *
 * This file is proprietary to Indie Semiconductor.
 * All rights reserved. Reproduction or distribution, in whole
 * or in part, is forbidden except by express written permission
 * of Indie Semiconductor.
 *
 * @file lin_device.c
 */

#include <stdint.h>
#include <string.h>
#include "rugby.h"
#include "hwcfg.h"
#include "lin_stack.h"
#include "lin_device.h"
#include "timer_device.h"
#include "errno.h"
#include <appConfig.h>

lin_cb_func_t lins_callback = NULL;
lin_cb_func_t linm_callback = NULL;

uint8_t deviceFrameTableSize = 0;
LIN_Device_Frame_t *deviceFrameTable = NULL;

typedef struct {
    uint16_t divider;
    uint16_t multiplier;
    uint8_t prescale;
} lin_speed_setting_t;
/**********************************************************************  
 *    Local definitions
 *********************************************************************/   

/**
 * At 16MHz system clock, the clock divider settings for each LIN speed.
 */
static const lin_speed_setting_t lin_speed_map[E_LIN_BAUDRATE_MAX + 1] = {
    [E_LIN_BAUDRATE_9600] = {.divider = 200,  .multiplier = 2, .prescale = 1},
    [E_LIN_BAUDRATE_19200] = {.divider = 200, .multiplier = 0, .prescale = 1},// slave use this
};


 void lins_SetTiming(LIN_BaudRate_t BaudRate)
{
    LINS_SFRS->BTDIV07          = lin_speed_map[BaudRate].divider & 0xFF;
    LINS_SFRS->BITTIME.BTDIV8   = lin_speed_map[BaudRate].divider >> 8;
    LINS_SFRS->BITTIME.PRESCL   = lin_speed_map[BaudRate].prescale;
    
    //configure the Bit Rate for 19200Hz
//    LINS_SFRS->BTDIV07 = 0xC8;  
//    LINS_SFRS->BITTIME.BTDIV8 = 0x0;  
//    LINS_SFRS->BITTIME.PRESCL = 0x1; 
}


int8_t LINS_WriteBuffer(uint8_t *buff, uint8_t dataLength)
{
    if (dataLength > 8){
        return -1;
    }
    for (uint8_t i = 0; i < dataLength; i++){
        LINS_SFRS->DATABUFF[i].DATA = buff[i];
    }
    return 0;
}


int8_t LINS_ReadBuffer(uint8_t *buff, uint8_t dataLength)
{
    if (dataLength > 8){
        return -1;
    }
    for (uint8_t i = 0; i < dataLength; i++){
        buff[i] = LINS_SFRS->DATABUFF[i].DATA;
    }
    return 0;
}



void LINS_Init(void)
{
    /* Config GPIO to LIN mode, enable transmission */

    
//    IOCTRLA_SFRS->LIN.LINS_HWMODE       = 1;     // Hardware Mode Enabled. LIN slave peripheral writes/read the LIN I/O pin.
//    IOCTRLA_SFRS->LIN.LINS_ENA          = 1;     // Enable LIN
//    IOCTRLA_SFRS->LIN.LINS_RXENA        = 1;     // LIN receive enable.
//    IOCTRLA_SFRS->LIN.LINS_TXENA        = 1;     // LIN transmit enable.
//    IOCTRLA_SFRS->LIN.LINS_PU30K_ENA    = 1;     // LIN 30K pullup enable.
    IOCTRLA_SFRS->LIN1.HWMODE = 1;
    IOCTRLA_SFRS->LINLP.BIAS1 = 1;
    IOCTRLA_SFRS->LIN1.RXENA = 1;
    IOCTRLA_SFRS->LIN1.TXPOL = 1;
    IOCTRLA_SFRS->LIN1.SPU30K = 1;
    IOCTRLA_SFRS->LINMUX.BYPASS = 0;//1: disable Master function, 0: enable Master function on LINOUT pin
    IOCTRLA_SFRS->LINMUX.M1S2 = 0;  //LIN Master/Slave Port Select. Default '0 assume LIN1 port as Slave and LIN2 port as Master. if set to '1 this will assume the LIN1 port as Master and LIN2 port as Slave.
  
    LINS_SFRS->BUSTIME.BUSINACTIVE      = E_LIN_TIME_INACTIVE_SEC_4;
    LINS_SFRS->BUSTIME.WUPREPEAT        = E_LIN_TIME_WAKEUP_REPEAT_MS_180;
    LINS_SFRS->CTRL.RSTERR              = 1;    //reset error
    LINS_SFRS->CTRL.RSTINT              = 1;    //reset interrupt
    
    lins_SetTiming(E_LIN_BAUDRATE_19200);       // set the maximum supported baud rate

}


void LINS_RegisterServices(LIN_Device_Frame_t *lin_cmd_table, uint8_t size)
{
    deviceFrameTable = lin_cmd_table;
    deviceFrameTableSize = size;
}

void LINS_RegisterLINS_IRQ(lin_cb_func_t cb)
{
    lins_callback = cb;
    NVIC_EnableIRQ(LIN_Slave_IRQn);
}


void LINS_UnRegisterLINS_IRQ(void)
{
    lins_callback = NULL;
    NVIC_DisableIRQ(LIN_Slave_IRQn);
}

/**
 * @brief Check if the message id is in the frame list
 *
 * @param dev The pointer to LIN device.
 * @param id The ID of received frame.
 * @return the index of the ID the the supported frame list, or -1 if not supported.
 */
int32_t is_valid_frame(LIN_Device_Frame_t *frameTable, uint8_t id)
{
    uint8_t i;
    if ((id == LIN_FRAME_ID_UNASSIGN) || (id == LIN_FRAME_ID_DONOTCARE)){
        return -1;
    }
    for (i = 0; i < deviceFrameTableSize; i++){
        if (frameTable[i].frame_id == id){
              return i;
        }
    }
    return -1;
}

void LIN_Slave_Handler(void)
{
    static int32_t index = 0;
    uint8_t status = (uint8_t)LINS_SFRS->STATUS.WORD;

    if (status & E_LIN_STATUS_DATA_REQ){
        index = is_valid_frame(deviceFrameTable, LINS_SFRS->ID);
        if (index >= 0){
            if (deviceFrameTable[index].msg_type == LIN_MSG_TYPE_RX){
                LINS_SFRS->CTRL.TRANSMIT = 0;// it's a data receive operation
                LINS_SFRS->DL.LENGTH = deviceFrameTable[index].length;
                LINS_SFRS->DL.ENHCHK = deviceFrameTable[index].checksum;
                LINS_SFRS->CTRL.DATAACK = 1;
            }else{
//                DEBUG_OUT("ENHCHK:%x\r\n",deviceFrameTable[index].checksum);
                //if (deviceFrameTable[index].responseValid){
                //    deviceFrameTable[index].responseValid = false;
                    LINS_SFRS->ID = deviceFrameTable[index].frame_id;
                    LINS_SFRS->CTRL.TRANSMIT = 1;// it's data request operation
                    LINS_SFRS->DL.LENGTH = deviceFrameTable[index].length;
                    LINS_SFRS->DL.ENHCHK = deviceFrameTable[index].checksum;
                    LINS_WriteBuffer(deviceFrameTable[index].data, deviceFrameTable[index].length);
                    LINS_SFRS->CTRL.DATAACK = 1;
                //}
            }
        }else{
            LINS_SFRS->CTRL.STOP = 1;
        }
    }else if (status & E_LIN_STATUS_ERROR){
    }else if (status & E_LIN_STATUS_IDLE_TIMEOUT){
        LINS_SFRS->CTRL.SLEEP = 1;
    }else if (status & E_LIN_STATUS_WAKEUP){
    }else if (status & E_LIN_STATUS_COMPLETE){//receive finished
        LINS_ReadBuffer(deviceFrameTable[index].data, deviceFrameTable[index].length);
        if(lins_callback){
            lins_callback(&deviceFrameTable[index]);
        }
    }
    LINS_SFRS->CTRL.RSTERR  = 1;    //reset error
    LINS_SFRS->CTRL.RSTINT  = 1;    //reset interrupt
}





LIN_Device_Frame_t linmFrame;

int8_t LINM_WriteBuffer(uint8_t *buff, uint8_t dataLength)
{
    if (dataLength > 8){
        return -1;
    }
    for (uint8_t i = 0; i < dataLength; i++){
        LINM_SFRS->DATABUFF[i].DATA = buff[i];
    }
    return 0;
}


int8_t LINM_ReadBuffer(uint8_t *buff, uint8_t dataLength)
{
    if (dataLength > 8){
        return -1;
    }
    for (uint8_t i = 0; i < dataLength; i++){
        buff[i] = LINM_SFRS->DATABUFF[i].DATA;
    }
    return 0;
}


void linm_SetTiming(LIN_BaudRate_t BaudRate)
{
    LINM_SFRS->BTDIV07 = lin_speed_map[BaudRate].divider & 0xFF;
    LINM_SFRS->BITTIME.BTDIV8 = lin_speed_map[BaudRate].divider >> 8;
    LINM_SFRS->BITTIME.BTMULT = lin_speed_map[BaudRate].multiplier;
    LINM_SFRS->BITTIME.PRESCL = lin_speed_map[BaudRate].prescale;
}


void LINM_Init(void)
{
    IOCTRLA_SFRS->LIN2.HWMODE = 1;
    IOCTRLA_SFRS->LINLP.BIAS2 = 1;
    IOCTRLA_SFRS->LIN2.RXENA  = 1;
    IOCTRLA_SFRS->LIN2.TXPOL  = 1;  //As the TXD connects to PullDown in trx
    IOCTRLA_SFRS->LIN2.MPU1K   = 1;   
    IOCTRLA_SFRS->LINMUX.BYPASS = 0;// 1: disable Master function, 0: enable Master function on LINOUT pin
    
    SYSCTRLA_SFRS->LIN.TXIPD2 = 0;
    SYSCTRLA_SFRS->LIN.TXSLEW2 = 0;

    LINM_SFRS->CTRL.RSTERR              = 1;
    LINM_SFRS->CTRL.RSTINT              = 1;
    
    linm_SetTiming(E_LIN_BAUDRATE_19200);
}

void LINM_RegisterLINM_IRQ(lin_cb_func_t cb)
{
    linm_callback = cb;
    NVIC_EnableIRQ(LIN_Master_IRQn);
}


void LINM_UnRegisterLINM_IRQ(void)
{
    linm_callback = NULL;
    NVIC_DisableIRQ(LIN_Master_IRQn);
}

int8_t LINM_SendFrame(LIN_Device_Frame_t *frame)
{
    linmFrame = *frame;
    LINM_SFRS->ID        = frame->frame_id;
    LINM_SFRS->DL.ENHCHK = frame->checksum;
    if (frame->msg_type == LIN_MSG_TYPE_TX){
        LINM_SFRS->DL.LENGTH = frame->length;
        LINM_SFRS->CTRL.TRANSMIT = 1;
    }else{
        LINM_SFRS->DL.LENGTH = frame->length;
        LINM_SFRS->CTRL.TRANSMIT = 0;
    }
    LINM_WriteBuffer(frame->data, frame->length);
    LINM_SFRS->CTRL.STARTREQ = 1;
    
    return 0;
}

void LIN_Master_Handler(void)
{
    uint8_t status   = (uint8_t)LINM_SFRS->STATUS.WORD;
    if (status & E_LIN_STATUS_ERROR){
    }else if(status & E_LIN_STATUS_WAKEUP){
    }else if(status & E_LIN_STATUS_COMPLETE){
        if (linmFrame.msg_type == LIN_RX){
            linmFrame.length   = LINM_SFRS->DL.LENGTH;
            LINM_ReadBuffer(linmFrame.data, LIN_BUFF_SIZE);
            if (linm_callback){
                linm_callback(&linmFrame);
            }
        }
    }
    LINM_SFRS->CTRL.RSTERR              = 1;    //reset error
    LINM_SFRS->CTRL.RSTINT              = 1;    //reset interrupt
}

void WULINM_Handler(void)
{
  
}




